Изменяется начальное состояние контекста React - JavaScript / React

Я делаю раздел руководства. Что делает этот раздел руководства - отображает массив процессов. Внутри каждого процесса есть массив шагов, внутри каждого шага - массив вариантов. Пользователь выбирает вариант на одном из шагов, он переводит их на следующий шаг корреляции. Если пользователь выберет опцию на шаге 2, он может перейти к шагу 3 или вернуться к шагу 1. Это зависит от идентификатора.

С учетом всего сказанного у меня проблемы с тем, что мой процесс мутирует меня. Я использую React Context как глобальное состояние. Когда пользователь выбирает вариант, я беру этот идентификатор, а затем фильтрую назначенный объект по этому идентификатору. Так что мне нужно оставить только этот процесс с этим конкретным шагом. Происходит то, что мое исходное глобальное состояние каким-то образом мутирует. Мне что-то здесь не хватает, так как я новичок в React.

Ps - В данный момент я не использую никаких сервисов, поэтому я просто скопировал JSON в исходное состояние в context.js

context.js

import React, { Component } from 'react'
// import axios from 'axios'

const Context = React.createContext()
const reducer = (state, action) => {
    switch(action.type){
        case 'SEARCH_PROCESS':
        return {
            ...state,
            guides: action.payload
        }
        default:
        return state
    }
}

export class Provider extends Component {

    state = {
        guides: [
            {
                "processName": "Support Messaging",
                "steps": [{
                    "id": "15869594739302",
                    "title": "step one", 
                    "options": [{
                        "nextStep": "4767fn-47587n-2819am-9585j,04956840987", 
                        "text": "Option 1 text", 
                        "type": "option"
                      },
                      {
                        "nextStep": "4767fn-47587n-2819am-9585j,04956840987", 
                        "text": "Option 2 text",
                        "type": "option"
                      },
                      {
                        "nextStep": "", 
                        "text": "Option 3 text",
                        "type": "option"
                      }
                    ]
                  },
                  {
                    "id": "04956840987",
                    "title": "step two", 
                    "options": [{
                        "nextStep": "4767fn-47587n-2819am-9585j,15869594739302",
                        "text": "Return to step1", 
                        "type": "option"
                      },
                      {
                        "nextStep": "", 
                        "text": "Option 2 text",
                        "type": "option"
                      },
                      {
                        "nextStep": "",
                        "text": "Option 3 text",
                        "type": "option"
                      }
                    ]
                  }
                ],
                "owner": "bob",
                "id": "4767fn-47587n-2819am-9585j",
                "lastUpdated": 154222227099000, 
                "tags": ["Tag1", "Tag2", "Tag3"]
              }
                ],
                "owner": "bob",
                "id": "4767fn-47587n-2819am-9585x",
                "lastUpdated": 154222227099000, 
                "tags": ["Tag1", "Tag2", "Tag3"]
              }
        ],
        initialGuide: [
            {
                "processName": "Support Messaging",
                "steps": [{
                    "id": "15869594739302",
                    "title": "step one", 
                    "options": [{
                        "nextStep": "4767fn-47587n-2819am-9585j,04956840987", 
                        "text": "Option 1 text", 
                        "type": "option"
                      },
                      {
                        "nextStep": "4767fn-47587n-2819am-9585j,04956840987", 
                        "text": "Option 2 text",
                        "type": "option"
                      },
                      {
                        "nextStep": "", 
                        "text": "Option 3 text",
                        "type": "option"
                      }
                    ]
                  },
                  {
                    "id": "04956840987",
                    "title": "step two", 
                    "options": [{
                        "nextStep": "4767fn-47587n-2819am-9585j,15869594739302",
                        "text": "Return to step1", 
                        "type": "option"
                      },
                      {
                        "nextStep": "", 
                        "text": "Option 2 text",
                        "type": "option"
                      },
                      {
                        "nextStep": "",
                        "text": "Option 3 text",
                        "type": "option"
                      }
                    ]
                  }
                ],
                "owner": "bob",
                "id": "4767fn-47587n-2819am-9585j",
                "lastUpdated": 154222227099000, 
                "tags": ["Tag1", "Tag2", "Tag3"]
              }
        ],

        dispatch: action => this.setState(state => reducer(state, action))
    }



    render() {
        return (
            <Context.Provider value={this.state}>
                {this.props.children}
            </Context.Provider>
        )
  }
}

export const Consumer = Context.Consumer;

Guides.js

import React, { Component } from 'react'
import { Consumer } from '../../context'
import Process from './Process'

class Guides extends Component {
  constructor (props) {
    super(props)
    this.state = {
      contextValue: [],
      searchData: props.location.data
    }
  }

  render () {
      console.log(this.props.location.data, this.state, 'logging state and props on guides')
    //   this.state.searchData = this.props.location.data

    return (
      <Consumer>
        {value => {

          return (
            <React.Fragment>
              <div className="content-wrapper">
                <h1>Guide Me</h1>
                <div className="ms-Grid times--max-width" dir="ltr">
                  <div className="ms-Grid-row">
                    <div className="profile--wrapper ms-Grid-col ms-sm12 ms-md12 ms-lg5">
                      {value.guides.map(item => {
                        return <Guide key={item.id} guide={item} processValue={value.guides} initialGuide={value.initialGuide}/>
                      })}
                    </div>
                  </div>
                </div>
              </div>
            </React.Fragment>
          )
        }}
      </Consumer>
    )
  }
}

export default Guides

Process.js

import React, { Component } from 'react'
import GuideSteps from './Guide-Steps'
import { Consumer } from '../../context'

class Process extends Component {
  constructor(props) {
    super(props)
    this.state = {
      processName: this.props.guide.processName,
      process: this.props.guide,
      steps: this.props.guide.steps,
      selectedIndex: 0,
      selectedStep: '',
      processValue: this.props.processValue,
      initialGuide: this.props.initialGuide
    }

    this.displayStep = this.displayStep.bind(this)
  }

  displayStep = (res, dispatch) => {
    this.setState({ selectedStep: res })
  }

  render() {

    const { steps, selectedIndex, process, processName, processValue, initialGuide } = this.state


    return (
        <Consumer>
          {value => {
          return (
            <div>
              <h2 className="profile--sub-header--bold">{processName}</h2>

              <GuideSteps
                key={this.props.guide.steps[selectedIndex].id}
                selectedStep={this.props.guide.steps[selectedIndex]}
                stepValue={this.displayStep}
                process={process}
                processValue={processValue}
                initialGuide={initialGuide}
              />
            </div>
          )
          }}
          </Consumer>
          )

  }
}

export default Process

Guide-Steps.js

import React, { Component } from 'react'
import { ChoiceGroup } from 'office-ui-fabric-react/lib/ChoiceGroup'
import { Consumer } from '../../context'

class GuideSteps extends Component {

    constructor(props) {
        super(props);
        this.state = {
            process: [],
            selectedStep: this.props.selectedStep,
            dispatch: '',
            processValue: this.props.processValue,
            initialGuide: ''
        }

        this._onChange = this._onChange.bind(this)
    }

  _onChange = (ev, option) => {
    // this.props.stepValue(option.value.nextStep)
    const { dispatch , initialGuide } = this.state

    let optionArray = option.value.nextStep.split(',')

    let processArray = this.state.process.filter(item => {
        return item.id === optionArray[0]
    })

    let stepArray = processArray[0].steps.filter(item => {
        return item.id === optionArray[1]
    })

    console.log(stepArray, processArray, this.state.process, 'logging step array before setting')

    processArray[0].steps = stepArray

    console.log(stepArray, processArray, this.state.process, 'logging step array after setting')


    dispatch({
        type: 'SEARCH_PROCESS',
        payload: processArray
      })
  }

  render() {
    let options = []
    {
      this.props.selectedStep.options.map(item => {
        return options.push({
          key: item.text,
          text: item.text,
          value: item
        })
      })
    }
    return (
        <Consumer>
            {value => {
                const { dispatch, guides, initialGuide } = value
                this.state.dispatch = dispatch
                console.log(value, 'logging initial guide in render')
                this.state.process = initialGuide
    return (
      <div>
        <ChoiceGroup
          className="defaultChoiceGroup"
          options={options}
          onChange={this._onChange}
        />
      </div>
    )
    }}
    </Consumer>
    )
  }
}

export default GuideSteps

При изменении GuideSteps я выполняю логику для фильтрации и настройки моего нового объекта.

ИЗМЕНИТЬ

Это устранило проблему, но я думаю, что это слишком дорого. Как мне решить эту проблему без повторной обработки массива.

update: (ev, option) => {
      const { initialGuide } = this.state

      if (option.value.nextStep !== null && option.value.nextStep !== '') {
        //split string
        const optionArray = option.value.nextStep.split(',')

        //filter process array
        const processArray = initialGuide.filter(process => {
          return process.id === optionArray[0]
        })
        //filter step array
        const stepArray = processArray[0].steps.filter(
          item => item.id === optionArray[1]
        )

        if(stepArray.length > 0 && stepArray !== null) {
            //get a copy of the process array so original is not mutated by the steps
            let stringC = JSON.stringify(processArray)
            let stringD = JSON.parse(stringC)
            stringD[0].steps = stepArray

            //issue might be here visually where setting the state happens quickly, therefore radio button visual does not display in time.
            setTimeout(() => {
                this.setState({ guides: stringD })
              }, 200)
        }
      }
    }, 

person userlkjsflkdsvm    schedule 30.11.2018    source источник
comment
Вам вопрос непонятен. какое значение мутирует и где именно вы видите эту мутацию. Можете ли вы создать демонстрацию с помощьюcodeandbox, и, поскольку вы отправляете обновление исходного состояния, ваше начальное состояние будет обновлено, так каково ожидаемое поведение здесь   -  person Shubham Khatri    schedule 03.12.2018


Ответы (1)


this.state.process = initialGuide

let processArray = this.state.process.filter...

processArray[0].steps = stepArray

Похоже, вы мутируете initialGuide через ссылку.

person lecstor    schedule 05.12.2018
comment
Ты прав. Я только что отредактировал свой вопрос, есть ли способ лучше, чем то, что у меня есть? - person userlkjsflkdsvm; 05.12.2018
comment
Я бы рекомендовал поменять массивы на объекты. Ключите все по идентификатору, по которому вы можете выполнять простой поиск. Если вам нужно поддерживать порядок, включите свойство nextThing или сохраните массив идентификаторов только для упорядочивания. - person lecstor; 06.12.2018
comment
Определенно создание ситуации, подобной хэш-карте, ускорит анализ массива / с. - person Jacob; 07.12.2018