React : Prop not updated inside of a map function - reactjs

Edit : Codesandbox here
Here is a simplified version on my parent component :
export default function Parent() {
// State
const [status, setStatus] = useState(1);
const [source, setSource] = useState('https://packagingeurope.com/downloads/7475/download/danone-05.03.20.jpg');
const [steps, setSteps] = useState([
{
title: 'Prediction Initiated',
value: 1,
loading: false,
completed: false,
active: false,
},
{
title: 'Prediction in Progress',
value: 2,
loading: false,
completed: false,
active: false,
},
{
title: 'Prediction Finished',
value: 3,
loading: false,
completed: false,
active: false,
},
]);
useEffect(() => {
if (status) {
const newSteps = steps;
newSteps[status - 1].active = true;
if (status > 1) {
newSteps[status - 2].completed = true;
}
if (status === 3) {
newSteps[status - 1].active = false;
newSteps[status - 1].completed = true;
}
setSteps(newSteps);
} else {
// Do nothing
console.log('No status match');
}
},
[status]);
return (
<div className="container-fluid">
<Timeline status={status} steps={steps} source={source} />
</div>
);
}
And here is my child component :
export default class Timeline extends React.Component {
renderSteps() {
const { steps } = this.props;
return steps.map((step, index) => {
console.log(step);
console.log(steps);
return (
<div key={`test${index}`}>
{step.title}
{step.active && <span>is active</span>}
{step.loading && <span>is loading</span>}
{step.completed && <span>is completed</span>}
</div>
);
});
}
render() {
const { status, steps, source } = this.props;
return (
<div className="timeline">
{this.renderSteps()}
</div>
</>
);
}
}
When i console.log steps props inside my child component, I see that they are correctly updated. (When status === 1, the first step active property is true)
But when i console.log step (inside my map function), none of the properties are updated. (When status === 1, the first step active property is false)
You can see on the capture below that something is not right (I already encountered this problem but unfortunately I can't remember how I solved it, and I am wondering if it's because of the useEffect hooks which I didn't use before this project.
Thank you for your help
Edit : Codesandbox here

Ok, I figured it out.
Since I got this code from another developer, I didn't understand everything that was implemented. I went to the React documentation to understand where the problem was coming from.
By passing a second parameter to the useEffect function, React can skip the effect and will not re-render the component for performance issues if there is not any change. As my status doesn't change (I change it manually for the moment), my component was not refreshing.
By adding [steps] width [status] inthe second parameter, everything works.
useEffect(() => {
if (status) {
const newSteps = steps;
newSteps[status - 1].active = true;
if (status > 1) {
newSteps[status - 2].completed = true;
}
if (status === 3) {
newSteps[status - 1].active = false;
newSteps[status - 1].completed = true;
}
setSteps(newSteps);
} else {
// Do nothing
console.log('No status match');
}
}, [status, steps]);

Related

React Native useSelector not updating state in Container

I'm working on a React Native app, and I'm having some issues regarding conditional rendering based on the Redux state, which I fetch using useSelector.
I have tried with and without the useEffect below (so with returning the component directly instead of setting a state), but the variables returned from useSelector do not seem to change when the state is updated. Since the states are loading at first, I end up in the latest else if, and get stuck there. I have to then refresh the app to get the actual values and get to the desired screen
const ContainerA = ({ navigation }) => {
const {
loginSuccess,
loginLoading,
accountType,
permissionsSuccess,
permissionsLoading,
} = useSelector((state) => state.accountInfo);
const [toRender, setToRender] = useState(null);
useEffect(() => {
if (loginSuccess) {
if (loginSuccess.success === 1 && accountType === 3) {
console.log('[Container] case 1');
setToRender(<PageA navigation={navigation} />);
// return <PageA navigation={navigation} />;
// return;
} else if (
(loginSuccess.success === 1 &&
(accountType === 1 || accountType === 2)) || (loginSuccess.success === 0)
) {
console.log('[Container] case 2');
navigation.navigate(SCREENS.CONTROL_PANEL);
}
} else if (loginLoading || permissionsLoading) {
console.log('[Container] case 4');
setToRender(<LoadingPage />);
// return <LoadingPage />;
// return;
}
}, [
loginSucess,
loginLoading,
accountType,
navigation,
permissionSuccess,
permissionsLoading,
]);
return toRender;
};
export default ContainerA;
Redux reducer:
case 'loginInit':
return updateState(state, {
loginLoading: true,
loginSuccess: null,
loginFail: null,
});
case 'loginSuccess':
return updateState(state, {
loginLoading: false,
loginSuccess: action.success,
});
case 'loginFail':
return updateState(state, {
loginLoading: false,
loginFail: action.error,
});
case 'permissionsInit':
return updateState(state, {
permissionsLoading: true,
accountType: null,
permissionsSuccess: null,
permissionsFail: null,
});
case 'permissionsSuccess':
return updateState(state, {
permissionsLoading: false,
permissionsSuccess: action.success,
accountType: action.success.success
? action.success.success
: action.success.errors,
});
case 'permissionsFail':
return updateState(state, {
permissionsLoading: false,
permissionsFail: action.error,
});
updateState function:
export const updateState = (state, updatedProps) => ({
...state,
...updatedProps,
});
Seems like I was executing the functions that do the checks in a place where I shouldn't get any successful response. I fixed it by calling those functions in the Navigator where I was sure to have the token, since those calls required it (and which were not doing the actual API call without it). The code remaining in ContainerA is:
const {
firstLoginSuccess,
firstLoginLoading,
accountType,
permissionsSuccess,
permissionsLoading,
} = useSelector((state) => state.accountInfo);
if (firstLoginSuccess) {
if (firstLoginSuccess.success === 1 && accountType === 3) {
return <FirstTimeLoginPage navigation={navigation} />;
} else if (
(firstLoginSuccess.success === 1 &&
(accountType === 1 || accountType === 2)) ||
firstLoginSuccess.success === 0
) {
navigation.navigate(SCREENS.CONTROL_PANEL);
}
} else if (firstLoginLoading || permissionsLoading) {
console.log('[FirstTimeLoginContainer] case 4');
}
return <LoadingPage />;

Problem with network timeout on PayPal SDK integration react-paypal-button-v2

Hello I am having an error implementing PayPal for my practice website. The error is shown below. Code otherwise works unless I click on smart button then leave it sitting. How can I error handle this? I am using react-paypal-button-v2. Button code is also shown here.
STATE CODE
function OrderScreen() {
...
const addPayPalScript = () => {
const script = document.createElement('script')
script.type = 'text/javascript'
// const ClIENT_ID = process.env.REACT_APP_PAYPAL_CLIENT_ID
script.src = `https://www.paypal.com/sdk/js?client-id=${CLIENT_ID}`
script.async = true
script.onload = () => {
setSdkReady(true)
}
document.body.appendChild(script)
}
useEffect(() => {
if(!order || successPay || order._id !== Number(orderId)) {
dispatch({type: ORDER_PAY_RESET})
dispatch(getOrderDetails(orderId))
} else if (!order.isPaid) {
if(!window.paypal) {
addPayPalScript()
} else {
setSdkReady(true)
}
}
if(error?.includes('token not valid')) {
dispatch(logout())
dispatch('login')
}
}, [dispatch, order, orderId, error, successPay])
const successPaymentHandler = (paymentResult) => {
dispatch(payOrder(orderId, paymentResult))
}
BUTTON CODE
{!sdkReady ? (
<Loader />
) : (
<PayPalButton
createOrder={(data, actions) => {
return actions.order.create({
purchase_units: [{
amount: {
currency_code: "USD",
value: order.totalPrice
},
...
}
}],
application_context: {
locale: 'us-US',
shipping_preference: 'SET_PROVIDED_ADDRESS',
}
});
}}
onSuccess={successPaymentHandler}
onError={(err)=>setErrorPayPal(err)}
/>
)}
</List
Most likely that 'stats' request is being blocked by some adblock or browser anti-tracking mechanism. Regardless of the specifics it's not a problem, ignore it completely.
Separately, react-paypal-button-v2 is not an official package. Consider using react-paypal-js instead.

React: TypeError: Cannot convert undefined or null to object

I am running a React application.
When I enter the page through routes(click button or link) page works fine but when I reload the page it crashes. I cannot see where the error is, the console and source show just blank, this is the server message console message.
server: Entrypoint index [big] = index.js index.1af9d975ff74be4d27d9.hot-update.js index.c93bf08301ec20c0dc85.js.map index.c93bf08301ec20c0dc85.js.map
server: 533 modules
server: backend debug read session { csrfToken: '0ae87c36d850008df20b58941bf89072', id: 2 }
server: backend debug {"data":{"currentUser":null}} <= undefined
server: backend debug {"data":{"currentUser":null}} <= undefined
server: backend debug read session {}
server: backend error TypeError: Cannot convert undefined or null to object
server: at getPrototypeOf (<anonymous>)
server: at hoistNonReactStatics (/Users/apple/Desktop/dev/bethabit/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js:71:38)
server: at resolveComponent (/Users/apple/Desktop/dev/bethabit/node_modules/#loadable/component/dist/loadable.cjs.js:332:3)
server: at InnerLoadable.loadSync (/Users/apple/Desktop/dev/bethabit/node_modules/#loadable/component/dist/loadable.cjs.js:189:24)
server: at new InnerLoadable (/Users/apple/Desktop/dev/bethabit/node_modules/#loadable/component/dist/loadable.cjs.js:138:17)
server: at processChild (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3048:14)
server: at resolve (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3013:5)
server: at ReactDOMServerRenderer.render (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3436:22)
server: at ReactDOMServerRenderer.read (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3395:29)
server: at renderToStaticMarkup (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3969:27)
server: at process (/Users/apple/Desktop/dev/bethabit/node_modules/#apollo/react-ssr/lib/react-ssr.cjs.js:38:16)
server: at process._tickCallback (internal/process/next_tick.js:68:7)
server: backend debug read session { id: 3 }
server: backend debug loading <= currentUser
server: backend debug loading <= currentUser
server: backend debug loading <= currentUser
server: backend debug {"data":{"currentUser":{"id":3,"username":"canercak#gmail.com","role":"user","isActive":true,"email":"canercak#gmail.com","__typename":"User"}}} <= currentUser
server: backend debug {"data":{"currentUser":{"id":3,"username":"canercak#gmail.com","role":"user","isActive":true,"email":"canercak#gmail.com","__typename":"User"}}} <= currentUser
server: backend debug {"data":{"currentUser":{"id":3,"username":"canercak#gmail.com","role":"user","isActive":true,"email":"canercak#gmail.com","profile":{"firstName":"Caner","lastName":"Çakmak","fullName":"Caner Çakmak","__typename":"UserProfile"},"auth":{"certificate":null,"facebook":{"fbId":null,"displayName":null,"__typename":"FacebookAuth"},"google":{"googleId":"104801577244556473676","displayName":"Caner Çakmak","__typename":"GoogleAuth"},"github":null,"linkedin":null,"__typename":"UserAuth"},"__typename":"User"}}} <= currentUser
server: backend debug read session {}
server: backend error TypeError: Cannot convert undefined or null to object
server: at getPrototypeOf (<anonymous>)
server: at hoistNonReactStatics (/Users/apple/Desktop/dev/bethabit/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js:71:38)
server: at resolveComponent (/Users/apple/Desktop/dev/bethabit/node_modules/#loadable/component/dist/loadable.cjs.js:332:3)
server: at InnerLoadable.loadSync (/Users/apple/Desktop/dev/bethabit/node_modules/#loadable/component/dist/loadable.cjs.js:189:24)
server: at new InnerLoadable (/Users/apple/Desktop/dev/bethabit/node_modules/#loadable/component/dist/loadable.cjs.js:138:17)
server: at processChild (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3048:14)
server: at resolve (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3013:5)
server: at ReactDOMServerRenderer.render (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3436:22)
server: at ReactDOMServerRenderer.read (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3395:29)
server: at renderToStaticMarkup (/Users/apple/Desktop/dev/bethabit/node_modules/react-dom/cjs/react-dom-server.node.development.js:3969:27)
server: at process (/Users/apple/Desktop/dev/bethabit/node_modules/#apollo/react-ssr/lib/react-ssr.cjs.js:38:16)
server: at process._tickCallback (internal/process/next_tick.js:68:7)
this is the code that runs. I tried removing currentuser, addhabit and many other things to just to understand where the error comes from. No results. Everything works fine if I enter the page through a link.
HabitaddView.web.jsx
const HabitAddView = ({ addHabit, t, currentUser }) => {
const renderMetaData = () => (
<Helmet
title={`${settings.app.name} - ${t('habit.title')}`}
meta={[
{
name: 'description',
content: t('habit.meta')
}
]}
/>
);
return (
<PageLayout>
{renderMetaData()}
<div className="row">
<div className="col-sm-12 padTop10">
<h2>{t(`habit.label.create_new`)}</h2>
</div>
</div>
<HabitForm onSubmit={onSubmit(addHabit)} currentUser={currentUser} />
</PageLayout>
);
};
HabitForm.web.jsx
const HabitForm = ({ values, handleSubmit, setFieldValue, submitting, t }) => {
const [habitType, sethabitType] = useState('Fitness Habits');
const [intervalText, setIntervalText] = useState(t('habit.field.interval'));
const [habitActions, sethabitActions] = useState([]);
const [habitFreqs, sethabitFreqs] = useState([]);
const [habitDurations, sethabitDurations] = useState([]);
const [habitIntervals, sethabitIntervals] = useState([]);
const [showInterval, setshowInterval] = useState(true);
const [showHabitDiv, setshowHabitDiv] = useState(false);
const [habitActionDisabled, sethabitActionDisabled] = useState(true);
let calendarComponentRef = React.createRef();
let calendarWeekends = true;
let [calendarEvents, setCalendarEvents] = useState([]);
var timeZones = moment.tz.names();
const handleHabitTypeChange = (type, value, setFieldValue) => {
setFieldValue(type, value);
resetValues();
Object.values(settings.habits.list).map(object => {
if (object.habit_type == value) {
const _habitActions = [];
Object.values(object.habit_actions).map(object2 => {
_habitActions.push(object2.title);
});
sethabitType(value);
sethabitActions(_habitActions);
setshowHabitDiv(false);
sethabitActionDisabled(false);
}
});
};
const updateIntervalText = worktype => {
if (worktype === 5) {
setIntervalText(t('habit.field.number'));
} else if (worktype === 6 || worktype === 7) {
setIntervalText(t('habit.field.proof'));
} else {
setIntervalText(t('habit.field.interval'));
}
};
const handleHabitActionChange = (type, value, setFieldValue) => {
const preparedValue = Array.isArray(value) ? value[0] : value;
setFieldValue(type, preparedValue);
const _habitFreqs = [];
const _habitDurations = [];
const _habitIntervals = [];
resetValues();
Object.values(settings.habits.list).map(object => {
if (object.habit_type == habitType) {
Object.values(object.habit_actions).map(object2 => {
if (object2.title == preparedValue) {
Object.values(object2.duration).map(object4 => {
_habitDurations.push(object4);
});
Object.values(object2.interval).map(object5 => {
_habitIntervals.push(object5);
});
sethabitFreqs(_habitFreqs);
sethabitIntervals(_habitIntervals);
sethabitDurations(_habitDurations);
setshowHabitDiv(true);
setFieldValue('timezone', moment.tz.guess());
setFieldValue('worktype', object2.worktype);
setFieldValue('description', object2.description);
setFieldValue('misc', JSON.stringify(object2.misc));
updateIntervalText(object2.worktype);
}
});
}
});
};
const handleValueChange = (type, value, setFieldValue) => {
const preparedValue = value;
setFieldValue(type, preparedValue);
let timezone = values.timezone;
let interval = values.interval;
let duration = values.duration;
if (type === 'timezone') {
timezone = value;
} else if (type === 'interval') {
interval = value;
} else if (type === 'duration') {
duration = value;
}
updateCalendarEvents(values.habit_action, timezone, interval, duration);
};
const empty = e => {
switch (e) {
case '':
case 0:
case '0':
case null:
case false:
case typeof this == 'undefined':
return true;
default:
return false;
}
};
const resetValues = () => {
values.duration = '';
values.interval = '';
values.timezone = '';
updateCalendarEvents(values.habit_action, values.timezone, values.interval, values.duration);
};
const updateCalendarEvents = (habit_action, timezone, interval, duration) => {
setCalendarEvents([]);
let events = [];
if (!empty(timezone) && !empty(interval) && !empty(duration)) {
var dur = JSON.parse(duration);
var int = JSON.parse(interval);
var times = dur.count;
var counter = 0;
for (var i = 0; i < times; i++) {
if (i % int.step === 0) {
counter += 1;
var obj = {
title: values.habit_action + ' ' + int.title,
start: moment
.tz(values.timezone)
.add(i + 1, 'days')
.format(),
allDay: true
};
let addObj = true;
if (values.worktype === 7 && i === 0) {
addObj = false;
}
if (dur.weekdays === true && (moment(obj.start).isoWeekday() === 6 || moment(obj.start).isoWeekday() === 7)) {
addObj = false;
}
if (
values.worktype === 7 &&
counter > 2 &&
counter < 10 &&
(habit_action === 'Stay Alcohol Free' || (habit_action === 'Stay Nicotine Free' && dur.count === 28))
) {
obj.start = moment
.tz(values.timezone)
.add(i + (counter - 1), 'days')
.format();
true;
}
if (addObj) {
events.push(obj);
}
}
}
if (habit_action === 'Stay Alcohol Free' && dur.count === 28) {
events.sort(function(a, b) {
return moment(b.start) - moment(a.start);
});
events.shift();
events.shift();
}
setCalendarEvents(events);
}
};
if (settings.habits.list) {
return (
<div className="row">
<div className="col-sm-5">
<div className="card">
<div className="card-body">
<Form name="habit" onSubmit={handleSubmit}>
<Field
name="habit_type"
component={RenderSelect}
type="select"
label={t('habit.field.habit_type')}
value={values.habit_type}
onChange={value => handleHabitTypeChange('habit_type', value, setFieldValue)}
cols={1}
>
<Option value={0}>--Please Select--</Option>
{Object.values(settings.habits.list).map((object, i) => {
return (
<Option key={i} value={object.habit_type}>
{object.habit_type}
</Option>
);
})}
</Field>
habitadd.jsx
class HabitAdd extends React.Component {
static propTypes = {
currentUser: PropTypes.object
};
constructor(props) {
super(props);
this.subscription = null;
this.state = { options: [] };
}
render() {
if ( this.props.currentUser) {
return <HabitAddView {...this.props} />;
} else {
return <div></div>;
}
}
}
export default compose(
graphql(ADD_HABIT, {
props: ({ ownProps: { history, navigation }, mutate }) => ({
addHabit: async (
habit_type,
habit_action,
duration,
interval,
timezone,
description,
worktype,
misc,
user_id
) => {
let habitData = await mutate({
variables: {
input: {
habit_type: habit_type,
habit_action: habit_action,
duration: duration,
interval: interval,
timezone: timezone,
description: description,
worktype: worktype,
misc: misc,
user_id: user_id
}
},
optimisticResponse: {
__typename: 'Mutation',
addHabit: {
__typename: 'Habit',
id: null,
habit_type: habit_type,
habit_action: habit_action,
duration: duration,
interval: interval,
timezone: timezone,
description: description,
worktype: worktype,
misc: misc,
user_id: user_id,
days: [],
bets: []
}
}
});
if (history) {
window.location.href = window.location.origin + '/habit/' + habitData.data.addHabit.id;
} else if (navigation) {
return navigation.push('HabitEdit', { id: habitData.data.addHabit.id });
}
}
})
}),
graphql(CURRENT_USER_QUERY, {
props({ data: { error, currentUser } }) {
if (error) throw new Error(error);
return { currentUser };
}
})
)(HabitAdd);
Thanks to Ace, I realized my mistake.
Added this code line on habitAdd render method
render() {
if ( this.props.currentUser) {
return <HabitAddView {...this.props} />;
} else {
return <div></div>;
}
}
The issue is with the currentUser. Consider initializing it. Before routing I am assuming you are setting the value for the currentUser or the currentUser is being set to a value, but on refresh, the value is reset, though the component will load, the value for currentUser isn't available hence the error.
VSCode is helpful here. Try running the node app in debug mode with --inspect option, with Pause on Exceptions option checked.
Alternatively you could look into configuring your app to have longer stack traces, or use something like longjohn [https://www.npmjs.com/package/longjohn] that aids with that.
It happened when inlineRequires are enabled in metro.config.js. Try disabling it and run the build again...
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: true,
inlineRequires: false
}
})
}
};

Interupt code and wait for user interaction in a loop - React

I am trying to implement an "add all" button in my react app. to do that, i pass this function to the onClick method of the button :
for (element in elements) {
await uploadfunction(element)
}
const uploadfunction = async (element) => {
if (valid) {
// await performUpload(element)
}
else if (duplicate) {
//show dialog to confirm upload - if confirmed await performUpload(element)
}
else {
// element not valid set state and show failed notification
}
}
const performUpload = async (element) => {
// actual upload
if(successful){
// set state
}else{
// element not successful set state and show failed notification
}
}
the uploadfunction can have three different behaviors :
Add the element to the database and update the state
Fail to add the element and update the state
Prompt the user with the React Dialog component to ask for confirmation to add duplicat element and update the state accordingly
My problem now is since i'm using a for loop and despite using Async/await , i can't seem to wait for user interaction in case of the confirmation.
The behavior i currently have :
The for loop move to the next element no matter what the result
The Dialog will show only for a second and disappear and doesn't wait for user interaction
Wanted behavior:
Wait for user interaction (discard/confirm) the Dialog to perform the next action in the loop.
How can i achieve that with React without Redux ?
Here is an example of a component that might work as an inspiration for you.
You might split it in different components.
class MyComponent extends Component {
state = {
items: [{
// set default values for all booleans. They will be updated when the upload button is clicked
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_1',
}, {
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_1',
}, {
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_2',
}],
performUpload: false,
};
onUploadButtonClick = () => {
this.setState(prevState => ({
...prevState,
items: prevState.items.map((item, index) => ({
isValid: validationFunction(),
isDuplicate: prevState.items.slice(0, index).some(i => i.data === item.data),
shouldUploadDuplicate: false,
data: item.data
})),
performUpload: true,
}), (nextState) => {
this.uploadToApi(nextState.items);
});
};
getPromptElement = () => {
const firstDuplicateItemToPrompt = this.getFirstDuplicateItemToPrompt();
const firstDuplicateItemIndexToPrompt = this.getFirstDuplicateItemIndexToPrompt();
return firstDuplicateItemToPrompt ? (
<MyPrompt
item={item}
index={firstDuplicateItemIndexToPrompt}
onAnswerSelect={this.onPromptAnswered}
/>
) : null;
};
getFirstDuplicateItemToPrompt = this.state.performUpload
&& !!this.state.items
.find(i => i.isDuplicate && !i.shouldUploadDuplicate);
getFirstDuplicateItemIndexToPrompt = this.state.performUpload
&& !!this.state.items
.findIndex(i => i.isDuplicate && !i.shouldUploadDuplicate);
onPromptAnswered = (accepted, item, index) => {
this.setState(prevState => ({
...prevState,
items: prevState.items
.map((i, key) => (index === key ? ({
...item,
shouldUploadDuplicate: accepted,
}) : item)),
performUpload: accepted, // if at last an item was rejected, then the upload won't be executed
}));
};
uploadToApi = (items) => {
if (!this.getFirstDuplicateItemToPrompt()) {
const itemsToUpload = items.filter(i => i.isValid);
uploadDataToApi(itemsToUpload);
}
};
render() {
const { items } = this.stat;
const itemElements = items.map((item, key) => (
<MyItem key={key} {...item} />
));
const promptElement = this.getPromptElement();
return (
<div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{itemElements}
</div>
<Button onClick={this.onUploadButtonClick}>Upload</Button>
{promptElement}
</div>
)
}
}

Want to uncheck the node of the tree structure in React JS

I have tree structure. What I needed is to uncheck the lover level of the tree when a level higher to it is checked.
But it is not working as I said above. What I have now is shown in the picture below
I clicked TPL
Clicked Accident Report(39)
3 During this time uncheck TPL
Code:
onCheck(checkedKeys) {
const {
FilterTaskList
} = this.props;
console.log(checkedKeys);
var checked2 = [];
if (checkedKeys.checked.length != 0) {
var addedkey = checkedKeys.checked[checkedKeys.checked.length - 1];
checked2 = _.filter(checkedKeys.checked, (o) => o.substring(0, addedkey.length + 1) != addedkey + "-");
}
checkedKeys.checked = checked2;
this.setState({
checkedKeys,
ischecked: true
});
let selectedLevel = 0;
let leveldata = [];
var checked = checkedKeys.checked;
const data = [];
const dataremove = [];
const AllLevel = [];
checked && checked.forEach((value) => {
var keys = value.split("-");
var task;
if (keys.length == 1) {
task = FilterTaskList[keys[0]];
data.push({
'TID': task.TID,
'TeamID': this.props.TeamId,
'RefID': task.REfID,
'FinClass': '',
'TLID': task.TLID,
'SelectionLevel': 2,
'SubTeamID': task.STID,
'Type': task.Type
});
AllLevel.push(2);
}
if (keys.length == 2) {
task = FilterTaskList[keys[0]].Chidrens[keys[1]];
data.push({
'TID': task.TID,
'TeamID': this.props.TeamId,
'RefID': task.REfID,
'FinClass': task.FinClass,
'TLID': task.TLID,
'SelectionLevel': 3,
'SubTeamID': task.STID,
'Type': task.Type
});
AllLevel.push(3);
}
if (keys.length == 3) {
task = FilterTaskList[keys[0]].Chidrens[keys[1]].Chidrens[keys[2]];
data.push({
'TID': task.TID,
'TeamID': this.props.TeamId,
'RefID': task.REfID,
'FinClass': task.FinClass,
'TLID': task.TLID,
'SelectionLevel': 4,
'SubTeamID': task.STID,
'Type': task.Type
});
AllLevel.push(4);
}
if (keys.length == 4) {
task = FilterTaskList[keys[0]].Chidrens[keys[1]].Chidrens[keys[2]].Chidrens[keys[3]];
data.push({
'TID': task.TID,
'TeamID': this.props.TeamId,
'RefID': task.REfID,
'FinClass': task.FinClass,
'TLID': task.TLID,
'SelectionLevel': 5,
'SubTeamID': task.STID,
'Type': task.Type
});
AllLevel.push(5);
}
The Id's of the tree as follows(backend)(Eg:):
2
2-0
2-0-0
To handle children's state from parent is basically react anti-pattern. You can solve it by registering children refs in parent and touch its checked status via refs. But at the and of the day, I always end up using redux in these cases, it is much cleaner.
Store the whole tree in global state and on firing uncheck action on parent the reducer handle the children's checked state based on parent id.
Meanwhile all the lines are connected components watching their own state based on their ids.
const reducer = (state, action) => {
let isUnchecking;
switch (action.type) {
case 'TOGGLE': return state.map(line => {
if (line.id === action.payload.id) {
isUnchecking = line.checked;
return { ...line, checked: !line.checked }
}
if (line.parent == action.payload.id && isUnchecking) {
return { ...line, checked: false }
}
return line;
});
default: return state;
}
}
const initialState = [
{ id: 1, checked: true, parent: null },
{ id: 2, checked: true, parent: 1 },
{ id: 3, checked: false, parent: 1 },
{ id: 4, checked: false, parent: null }
]
const store = Redux.createStore(reducer, initialState);
const Line = ({ checked, onClick, label, children }) => (
<li onClick={onClick}>
<span>{checked ? '[x] ' : '[_] '}</span>
{label}
<ul>{children}</ul>
</li>
)
const mapStateToProps = state => ({ lines: state })
const LineContainer = ReactRedux.connect(mapStateToProps)(props => {
const { id } = props;
return (
<Line
{...props}
onClick={e => {props.dispatch({ type: 'TOGGLE', payload: { id } }); e.stopPropagation();}}
checked={props.lines.find(line => line.id === id).checked}
/>
)
}
)
class App extends React.Component {
render(){
return (
<ReactRedux.Provider store={store}>
<LineContainer id={1} label="Accident Report">
<LineContainer id={2} label="TPL" />
<LineContainer id={3} label="WC" />
</LineContainer>
<LineContainer id={4} label="Administrative Supervisor" />
</ReactRedux.Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.1/react-redux.js"></script>
<div id="root"></div>

Resources