I have tried and I keep running into react 302 errors with multiple instances of react and issues with state. I am still learning react and I always get stuck when it comes to class and function components. I am trying to convert this into a class based component since that is what I am familiar with using and need to integrate into another class based component.
const { useState, useEffect, } = React;
const { useSelector } = ReactRedux;
import { React, AllWidgetProps, getAppStore, appActions, ReactRedux, WidgetProps, WidgetManager, IMState } from 'jimu-core';
import { Button, Label, Row, Col, Select, Option } from 'jimu-ui';
import defaultMessages from './translations/default';
/**
* This widget will show how to control widget state for a collapsible sidebar widget and a widget within the widget controller widget.
*/
export default function Widget(props: AllWidgetProps<{}>) {
// Establish state properties, initial values and their corresponding set state actions
const [sidebarWidgetId, setSidebarWidgetId] = useState(null as string);
const [openCloseWidgetId, setOpenCloseWidgetId] = useState(null as string);
const [sidebarVisible] = useState(true as boolean);
const [openness, setOpenness] = useState(false as boolean);
const [appWidgets, setAppWidgets] = useState({} as Object);
const [widgetsArray, setWidgetsArray] = useState([] as Array<any>);
const [sidebarWidgetsArray, setSidebarWidgetsArray] = useState([] as Array<any>);
// Get the widget state - because the sidebar state may change in the runtime, via Redux's useSelector hook
const widgetState = useSelector((state: IMState) => {
const widgetState = state.widgetsState[sidebarWidgetId];
return widgetState;
});
// Update the appWidgets property once, on page load
useEffect(() => {
const widgets = getAppStore().getState().appConfig.widgets;
setAppWidgets(widgets);
}, []);
// Update the widgetsArray and sidebarWidgetsArray properties every time appWidgets changes
useEffect(() => {
if (appWidgets) {
const widgetsArray = Object.values(appWidgets);
setWidgetsArray(widgetsArray);
setSidebarWidgetsArray(widgetsArray.filter(w => w.uri === 'widgets/layout/sidebar/'));
}
}, [appWidgets]);
// Toggle the sidebar widget
const handleToggleSidebar = (): void => {
// If widget state's collapse property is true, collapse
if (widgetState && widgetState.collapse === true) {
getAppStore().dispatch(appActions.widgetStatePropChange(
sidebarWidgetId,
'collapse',
!sidebarVisible
));
}
// If widget state's collapse property is false, expand
else if (widgetState && widgetState.collapse === false) {
getAppStore().dispatch(appActions.widgetStatePropChange(
sidebarWidgetId,
'collapse',
sidebarVisible
));
}
else {
alert(
defaultMessages.sidebarAlert
)
}
};
// Load the widget class prior to executing the open/close actions
const loadWidgetClass = (widgetId: string): Promise<React.ComponentType<WidgetProps>> => {
if (!widgetId) return;
const isClassLoaded = getAppStore().getState().widgetsRuntimeInfo?.[widgetId]?.isClassLoaded;
if (!isClassLoaded) {
return WidgetManager.getInstance().loadWidgetClass(widgetId);
} else {
return Promise.resolve(WidgetManager.getInstance().getWidgetClass(widgetId));
}
};
// Open widget method
const handleOpenWidget = (): void => {
// Construct the open action, then run the loadWidgetClass method, dipatch the open action
// and, finally, set the openness to true
const openAction = appActions.openWidget(openCloseWidgetId);
loadWidgetClass(openCloseWidgetId).then(() => {
getAppStore().dispatch(openAction);
}).then(() => { setOpenness(true) });
};
// Close widget method
const handleCloseWidget = (): void => {
// Construct the close action, then run the loadWidgetClass function, dipatch the close action
// and, finally, set the openness to false
const closeAction = appActions.closeWidget(openCloseWidgetId);
loadWidgetClass(openCloseWidgetId).then(() => {
getAppStore().dispatch(closeAction);
}).then(() => { setOpenness(false) });
};
// Handler for the openness toggle button
const handleToggleOpennessButton = (): void => {
// Check the openness property value and run the appropriate function
if (openness === false) { handleOpenWidget(); }
else if (openness === true) { handleCloseWidget(); }
else { console.error(defaultMessages.opennessError) }
};
// Handler for the sidebar selection
const handleSidebarSelect = evt => {
setSidebarWidgetId(evt.currentTarget.value);
};
// Handler for the open/close selection
const handleOpenCloseSelect = evt => {
setOpenCloseWidgetId(evt.currentTarget.value);
};
return (
<div className='widget-control-the-widget-state jimu-widget m-2' style={{ width: '100%', height: '100%', maxHeight: '800px', padding: '0.5em' }}>
<h6
title={defaultMessages.title}
>
{defaultMessages.title}
</h6>
{sidebarWidgetsArray && sidebarWidgetsArray.length > 0 &&
<Row className='p-2 justify-content-between align-items-center'>
<Col className='col-sm-6'>
<Label
title={defaultMessages.sidebarLabel}
>
{defaultMessages.sidebarLabel}
</Label>
<Select
defaultValue=''
onChange={handleSidebarSelect}
placeholder={defaultMessages.sidebarPlaceholder}
title={defaultMessages.sidebarPlaceholder}
>
{/* Use the sidebarWidgetsArray to populate the select's options */}
{
sidebarWidgetsArray.map((w) => <Option
value={w.id}
>
{w.label}
</Option>)
}
</Select>
</Col>
{sidebarWidgetId &&
<Col className='col-sm-6'>
<Button
onClick={handleToggleSidebar}
htmlType='submit'
type='primary'
title={defaultMessages.sidebarButtonLabel}
>
{defaultMessages.sidebarButtonLabel}
</Button>
</Col>
}
</Row>
}
{widgetsArray && widgetsArray.length > 0 &&
<Row className='p-2 justify-content-between align-items-center'>
<Col className='col-sm-6'>
<Label
title={defaultMessages.widgetControllerWidgetLabel}
>
{defaultMessages.widgetControllerWidgetLabel}
</Label>
<Select
defaultValue=''
onChange={handleOpenCloseSelect}
placeholder={defaultMessages.widgetControllerWidgetPlaceholder}
title={defaultMessages.widgetControllerWidgetPlaceholder}
>
{/* Use the widgetsArray to populate the select's options */}
{
widgetsArray.map((w) => (
<Option
value={w.id}
>
{w.label}
</Option>
))
}
</Select>
</Col>
{openCloseWidgetId &&
<Col className='col-sm-6'>
<Button
onClick={handleToggleOpennessButton}
htmlType='submit'
type='primary'
title={defaultMessages.widgetControllerWidgetButton}
>
{defaultMessages.widgetControllerWidgetButton}
</Button>
</Col>
}
</Row>
}
</div>
);
};
Related
I've tried to use useMemo and useEffect in my custom hooks to prevent that from re-rendering ( doing cpu heavy job) which is parsing the JWT and returning the user permissions.
But it re-renders eveerytime that my menu items re-render and runs the usememo too.
Is there a way to avoid it from re-calculating ?
The code:
const usePermission = (controller?: string) => {
const [permoactions, setPermoactions] = React.useState([]);
const fetchPermoactions = () => {
try {
// parse jwt token and return permissions
} catch (error: any) {
throw new Error(error.message);
}
};
useMemo(() => {
fetchPermoactions();
console.count("memo");
}, [1]);
const access = React.useMemo(() => {
// make an permissions object tree
}, [permoactions]);
return controller ? access[controller] : access;
};
export default usePermission;
The usePermission hook is used in the pages where I need to validate for example if a user should see a button or have the ability to delete a line like this:
{permissions?.Create && (
<Grid item xs={2} sx={{ textAlign: "left" }}>
<Button
variant="contained"
onClick={handleAddClick}
endIcon={<AddIcon sx={{ marginRight: 2 }} />}
>
افزودن
</Button>
</Grid>
)}
It's also used in the sidebar to not show the menus that the user doesn't have access to like:
children = children.filter((child: any) => {
let AccessList: string[] = child.name.split(",");
let hasAccess = false;
AccessList.forEach((Access) => {
if (userAccess[Access]?.View) {
hasAccess = true;
} else {
hasAccess = false;
return;
}
return hasAccess && children;
});
return hasAccess ? child : null;
});
I am trying to make a Sudoku solver. I have a parent component Board and a child component Possible which shows available options for any box in the board. I passed the state of board,selected(selected box position in the board) and function to update board as props. But when I try to change board from Possible it doesn't change unless the selected box selected is changed from parent component. I am trying to change board element from child component.
Here is my Board component.
import React, { useState } from 'react';
import Possibles from './avails.jsx';
import solve, { initBoard } from '../help';
function Board() {
const [msg, setmsg] = useState('');
const [solved, setSolved] = useState(false);
const [grid, setGrid] = useState(initBoard());
const [selected, setSelected] = useState([0, 0]);
const solveBoard = () => {
const solution = solve(grid);
setGrid(solution);
setSolved(true);
let a = true;
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
// console.log(i,j)
if (grid[i][j] === 0) {
// console.log(grid[i][j]);
a = false;
}
}
}
if (!a) {
setmsg('Invalid Board!');
} else setmsg('Here is your solution!');
};
const updatePosition = (row, col) => {
setSelected([row, col]);
};
const resetBoard = () => {
setGrid(initBoard());
setSolved(!solved);
setmsg('');
};
return (
<div className="board">
Sudoku Solver
{grid.map((row, index) => (
<div key={index} className="row">
{row.map((el, i) => (
<button
type="button"
key={i}
className={selected[0] === index && selected[1] === i ? 'el selected' : 'el'}
onClick={() => { updatePosition(index, i); }}
>
{el === 0 ? '' : el}
</button>
))}
</div>
))}
{setSolved
? <Possibles board={grid} pos={selected} setGrid={setGrid} setPos = {setSelected}/> : ''}
<button type="button" className="btn" onClick={solveBoard}>Solve</button>
<button type="button" className="btn" onClick={resetBoard}>Reset</button>
<div>{msg}</div>
</div>
);
}
export default Board;
And here is my child component Possibles.
import React, { useState, useEffect } from 'react';
import { getAvailableNumbers } from '../help';
function Possibles({ board, pos, setGrid }) {
const [possibles, setPosibles] = useState([]);
useEffect(() => {
const avails = getAvailableNumbers(board, pos);
avails.unshift(0);
setPosibles(avails, board, possibles);
}, [pos,board]);
const updateGrid = (opt) => {
// setPosibles((prev) => prev.filter((x) => x !== opt || x === 0));
board[pos[0]][pos[1]] = opt;
setGrid(board);
};
return (
<div>
{possibles.map((opt) => (
<button type="button" key={opt} onClick={() => { updateGrid(opt); }}>{opt === 0 ? 'X' : opt}</button>
))}
</div>
);
}
export default Possibles;
please change your 'updateGrid' function to -
const updateGrid = (opt) => {
// setPosibles((prev) => prev.filter((x) => x !== opt || x === 0));
board[pos[0]][pos[1]] = opt;
board = [...board]; // this will make parent component rerender
setGrid(board);
};
input to setPosibles should be an array according to first line of 'Possibles' component -
setPosibles(avails, board, possibles);
Edit - react basically uses shallow comparison for props, state to detect new available change. By using a spread operator we are a creating new array with a new memory location, this let react know about new change and rerendering is triggered.
This is a model simulated from a real project.
Clicking undo button should change the state of checkboxes to the state before user changes them.
When I check/uncheck a checkbox, appRef.current gets updated! why?
the effect hook in App.jsx is only called once because its dependencies array is empty.
Confusion becomes more when you click check/uncheck all button then try to change the checkboxes and hit undo button but this time it works!!!
App.jsx
import { useEffect, useRef, useState } from 'react'
import Box from './box'
const App = () => {
const [claims, setClaims] = useState()
const appRef = useRef()
const crudTrue = { Create: true, Read: true, Update: true, Delete: true }
const crudFalse = { Create: false, Read: false, Update: false, Delete: false }
const defaultClaims = {
resource1: { ...crudFalse },
resource2: { ...crudFalse, Read: true },
resource3: { ...crudFalse, Read: true },
resource4: { ...crudFalse }
}
useEffect(() => {
// this effect should be called once
// appRef should be set once
appRef.current = { ...defaultClaims }
setClaims({ ...defaultClaims })
}, [])
return (
<Box
claims={claims}
// cloning to be extra sure it is not mutated
boxRef={{ ...appRef }}
onChange={setClaims}
crudTrue={crudTrue}
crudFalse={crudFalse}
/>
)
}
export default App
box.jsx
import { createElement, useEffect, useRef } from 'react'
import CheckBox from './checkBox'
const Box = ({ claims, boxRef, onChange, crudTrue, crudFalse }) => {
const checkRef = useRef()
useEffect(() => (checkRef.current = boxRef.current), [boxRef])
const handleChange = ({ currentTarget: checkbox }) => {
const xclaims = { ...claims }
const parts = checkbox.name.split('-')
xclaims[parts[0]][parts[1]] = checkbox.checked
onChange(xclaims)
}
const handleAll = () => {
const xclaims = { ...claims }
let isAnyClaimFalse = Object.keys(xclaims).some((res) =>
Object.keys(xclaims[res]).some((c) => xclaims[res][c] === false)
)
for (let res in xclaims) xclaims[res] = isAnyClaimFalse ? { ...crudTrue } : { ...crudFalse }
onChange(xclaims)
}
const handleUndo = () => onChange(checkRef.current)
const renderChecks = () => {
const children = []
for (let res in claims)
for (let key in claims[res])
children.push(
<>
{key === 'Create' && res}
<CheckBox
key={res + '-' + key}
name={res + '-' + key}
label={key}
checked={claims[res][key]}
onChange={handleChange}
/>
{key === 'Delete' && <br />}
</>
)
return createElement('div', [], ...children)
}
return (
<>
<button onClick={handleUndo}>undo</button>
<button onClick={handleAll}>check/uncheck All</button>
{renderChecks()}
</>
)
}
export default Box
checkbox.jsx
const CheckBox = ({ name, label, ...rest }) => (
<>
<input type='checkbox' id={name} name={name} {...rest} />
{label && <label htmlFor={name}>{label}</label>}
</>
)
export default CheckBox
Output is avilable here and codesandbox
Did not find the problem in your code, therefore I tried to solve it with my own solution. I changed some of the code completely. Maybe if you write back parts of it to yours you find out what was the root of the problem.
Here is the codesandbox link: https://codesandbox.io/s/using-react-hooks-useeffect-useref-1odx68
I've came across this, which helped me this as far as I've got. Though, I'm trying to hack together a simple status component that instead of a checkbox, is just a div with styling to make it appear as a tiny dot that toggles between three strings, offline, wip and online onClick! Changing just the color upon change of state. (Practically speaking, I'll set an array of objects as offline and if toggled differently I'll store that preference.)
I'm just stuck trying to move away from a checkbox, I'll show you what I mean:
const STATUS_STATES = {
Online: "online",
Wip: "wip",
Offline: "offline",
};
function SomePage() {
const [status, setStatus] = useState(STATUS_STATES.Offline);
const handleChange = () => {
let updatedChecked;
if (status === STATUS_STATES.Online) {
updatedChecked = STATUS_STATES.Offline;
} else if (status === STATUS_STATES.Offline) {
updatedChecked = STATUS_STATES.Wip;
} else if (status === STATUS_STATES.Wip) {
updatedChecked = STATUS_STATES.Online;
}
setStatus(updatedChecked);
};
const Status = ({ value, onChange }) => {
const checkboxRef = React.useRef();
useEffect(() => {
if (value === STATUS_STATES.Online) {
console.log("online")
checkboxRef.current.checked = true;
checkboxRef.current.indeterminate = false;
} else if (value === STATUS_STATES.Offline) {
console.log("offline")
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
} else if (value === STATUS_STATES.Wip) {
console.log("wip")
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = true;
}
}, [value]);
return (
<label>
<input ref={checkboxRef} type="checkbox" onChange={onChange} />
{/* I need to replace the line above with the line below */}
{/* while escaping the label element it's wrapped within */}
<div
className={
(value === "offline" && "offline") ||
(value === "wip" && "wip") ||
(value === "online" && "online")
}
onChange={onChange}
/>
</label>
);
};
return (
<>
<Status value={status} onChange={handleChange} />
<p>Is checked? {status}</p>
</>
)
}
.status{
width: 6px;
height: 6px;
background: #ee4f4f;
border-radius: 50%;
}
Any advice to approach this more efficiently?
This tidies it up quite a bit
codebox: https://codesandbox.io/s/intelligent-gareth-7qvko?file=/src/App.js:0-1050
import "./styles.css";
import React, { useState, useEffect } from "react";
const STATUS_STATES = ["Online", "Wip", "Offline"];
const STATUS_COLORS = ["green", "orange", "red"];
const Dot = ({ color, onClick }) => (
<div
onClick={onClick}
style={{ borderRadius: 5, height: 10, width: 10, background: color }}
/>
);
const Main = () => {
const [status, setStatus] = useState(0);
const handleClick = () => {
const newStatus = (status + 1) % STATUS_STATES.length;
setStatus(newStatus);
};
const text = STATUS_STATES[status];
return (
<>
<Dot onClick={handleClick} color={STATUS_COLORS[status]} />
<div onClick={handleClick}>{text}</div>
</>
);
};
export default Main;
You will see to loop through the statuses, I have put them through in an array, and used the remainder operator to get the next index.
The useEffect logic only needed a simple if for each value which has been written in short hand which (I think) is more readable for variables.
You will also notice the onclick on the checkbox input is wrapped in a timeout with a 0ms wait. This is a workaround because I couldnt prevent the default checkbox behaviour from happening. This ensures the click handling is run after the native logic.
I also reduced your array to just an array of strings - This was just to simplify the example.
I have a list of checkboxes that receive a handler function, which changes the state when a checkbox is checked/unchecked. When I click on the checkbox, I am getting the following error:
TypeError: onChangePoll is not a function
I've tried:
Changing the handler function to an arrow function
Moving the checkbox component from it's modular file to place it inside the file where the function resides
Changed onChange to onClick
Traced it through the DOM as it renders
It seems that the handler function is being passed to the component correctly when rendering, but when I make the click, it seems to disappear.
For the code below, affiliates is an array that looks like this:
[
{
shortName: `a`
},
{
shortName: `b`
},
{
shortName: `c`
}
];
NewPoll.js
import React, { useState } from 'react';
import { Redirect } from "react-router";
import { AffiliateSelects } from './../helpers/FormElements';
import { affiliates } from './../helpers/Config';
import { initialNewPoll } from './../helpers/Config';
import { isAffiliates, validateNewPollModal } from './../helpers/FormValidator';
function NewPoll(props) {
const {
pollData,
type,
onChangePoll
} = props;
// Set headline for modal
const headline = type === `new` ? `Add a New Poll` : `Edit “${pollData.pollName.value}”`;
return(
<div className="modal-content">
<h2>{headline}</h2>
<form className="form__inputs form__inputs--modal">
<div className="form__inputs-row">
<label
className={pollData.affiliates.error ? `form__input-label form__input-label--error` : `form__input-label`}
htmlFor="affiliates"
>
{pollData.affiliates.error ? `Affiliates (${pollData.affiliates.error})` : `Affiliates`}
</label>
<AffiliateSelects
type="checkbox"
name="affiliates"
state={pollData.affiliates.value}
onChangePoll={onChangePoll}
/>
</div>
<div className="form__buttons-row">
<ul className="form__buttons-row-list">
<li className="form__buttons-row-item">
<button
className="button"
type="reset"
name="clear-filters"
onClick={(e) => onChangePoll({
name: `clearFields`
})}
>
Clear Fields
</button>
</li>
<li className="form__buttons-row-item">
<button
className="button"
type="reset"
name="create-poll"
onClick={(e) => onChangePoll({
name: `createPoll`
})}
>
{type === `new` ? `Create Poll` : `Save Edits`}
</button>
</li>
</ul>
</div>
</form>
</div>
)
}
export const NewPollModal = (props) => {
const {
pollData,
setPollData,
type
} = props;
const onChangePoll = (el) => {
// Validate the poll data being sent through
let validPoll;
switch(el.name) {
case `affiliates`:
validPoll = isAffiliates(el.value, pollData.affiliates.value);
break;
case `clearFields`:
validPoll = initialNewPoll;
break;
default:
break;
}
if (el.name === `createPoll`) {
// Check to make sure all of the fields are valid
const isPollValid = validateNewPollModal(pollData);
if (isPollValid.valid) {
// If they are valid, send the state to the poll edit page
setPollData({
...pollData,
valid: true
});
} else {
// If they are not valid, create errors on the form
setPollData(isPollValid.validPoll);
}
} else if (el.name === `clearFields`) {
setPollData(validPoll);
} else {
setPollData({
...pollData,
[`${el.name}`]: {
valid: validPoll.valid,
error: validPoll.error,
value: validPoll.value
}
});
}
}
if (pollData.valid) {
return(
<Redirect
to={{
pathname: "/poll",
state: {
affiliates: pollData.affiliates.value
}
}}
/>
)
} else {
return(
<NewPoll
pollData={pollData}
type={type}
onChangePoll={onChangePoll}
/>
)
}
}
FormElements.js
import React from 'react';
import { affiliates } from './Config';
function ListItem(props) {
const {
aff,
type,
state,
onChangePoll
} = props;
// If we are in edit mode, add the checkmark if the affiliate is in state
const checked = state.includes(aff) ? true : false;
const affiliates = {
name: `affiliates`,
value: aff
};
if (type === `checkbox`) {
console.log(onChangePoll);
return(
<li className="affiliate-selects__items">
<input
className="affiliate-selects__checkbox"
type={type}
name="affiliates"
id={`affiliates-${aff}`}
checked={checked}
onChange={() => onChangePoll(affiliates)}
/>
<label className="affiliate-selects__label" htmlFor={`affiliates-${aff}`}>{aff}</label>
</li>
)
} else if (type === `list`) {
return(
<li className="affiliate-selects__items">{aff}</li>
)
}
}
function List(props) {
const {
type,
state,
affList,
onChangePoll
} = props;
let listClass;
if (type === `checkbox`) {
listClass = `affiliate-selects affiliate-selects--checkbox`;
} else if (type === `list`) {
listClass = `affiliate-selects affiliate-selects--list`;
}
return(
<ul className={listClass}>
{affList.map((aff) =>
<ListItem
key={`affiliate-selects-${aff.shortName}`}
aff={aff.shortName}
type={type}
state={state}
onChangePoll={onChangePoll}
/>
)}
</ul>
)
}
/**
* Displays a list of affiliates, eiter as a checkbox list, or as a regular list.
* #param {String} props.type Will display the affiliates as a list of checkboxes for a display * list
* Ex: `checkbox` || `list`
* #param {Function} props.handleOnChange Event handler function that changes the items that are filtered
* (optional)
* #param {String} props.prefix Will prefix all input IDs so form inputs will work correctly
* (optional)
* #param {Array} props.affArray Array of affiliate string names
* Ex: [`nj`, `oregonlive`, `masslive`]
* (optional)
*/
export function AffiliateSelects(props) {
const type = props.type || `list`;
const state = props.state || [];
const {
affArray,
onChangePoll
} = props;
// If the user doens't pass in an array, list all of the affiliates from the config
let affList;
if (affArray === undefined || affArray.length === 0) {
affList = affiliates;
} else {
affList = affArray.reduce((accum, aff) => {
accum.push({shortName: aff});
return accum;
}, []);
}
return(
<List
type={type}
state={state}
affList={affList}
onChangePoll={onChangePoll}
/>
)
}
The expected behavior would be that the user clicks the checkbox next to an affiliate, and that affiliate gets validated by the handler and saved to state.